package org.springframework.beans.factory;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.util.StringValueResolver;

import java.io.IOException;
import java.util.Properties;

/**
 * @author derekyi
 * @date 2020/12/13
 */
//本身实现BeanFactoryPostProcessor
public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {

	public static final String PLACEHOLDER_PREFIX = "${";

	public static final String PLACEHOLDER_SUFFIX = "}";

	private String location;//这个location路径，我们在beandefinition创建的时候已经填充这个属性了，在xml里定义


	//这是他的方法，我们操作定义bean，肯定需要ioc容器，这样就能操作了
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		//加载属性配置文件，这个我们之前学过，就是资源和资源加载器的操作。这里已经读取了配置文件里所有的数据了
		Properties properties = loadProperties();

		//属性值替换占位符，传入俩，一个ioc，一个配置类对象
		processProperties(beanFactory, properties);

		//往容器中添加字符解析器，供解析@Value注解使用
		StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(properties);
		beanFactory.addEmbeddedValueResolver(valueResolver);
	}

	/**
	 * 加载属性配置文件
	 *
	 * @return
	 */
	private Properties loadProperties() {
		try {
			//里面就是资源加载器加载路径下的资源。没啥好说的
			DefaultResourceLoader resourceLoader = new DefaultResourceLoader();

			Resource resource = resourceLoader.getResource(location);//location是我们传入的路径，先得到资源对象
			Properties properties = new Properties();//配置类是新的类，源码
			properties.load(resource.getInputStream());
			return properties;
		} catch (IOException e) {
			throw new BeansException("Could not load properties", e);
		}
	}

	/**
	 * 属性值替换占位符
	 *
	 * @param beanFactory
	 * @param properties
	 * @throws BeansException
	 */

	private void processProperties(ConfigurableListableBeanFactory beanFactory, Properties properties) throws BeansException {
		//把所有的BeanDefinition的Name拿出来，存入集合里。集合名称集合
		//一个个遍历，是否有
		String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
		for (String beanName : beanDefinitionNames) {
			BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
			//我们发现我们这里操作的是beanDefinition，所以配置文件加载是把数据加载到了beanDefinition里
			resolvePropertyValues(beanDefinition, properties);
		}
	}

	private void resolvePropertyValues(BeanDefinition beanDefinition, Properties properties) {
		//得到当前beanDefinition的所有属性值，遍历
		PropertyValues propertyValues = beanDefinition.getPropertyValues();
		for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
			//得到属性名称对应的值
			Object value = propertyValue.getValue();
			if (value instanceof String) {
				//如果是字符串类型，进入下面的方法。为什么规定是字符串类型。
				//因为，${}属性占位符是字符串类型，我们要匹配他
				value = resolvePlaceholder((String) value, properties);
				//下面要进行赋值了
				propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), value));
			}
		}
	}

	private String resolvePlaceholder(String value, Properties properties) {
		//TODO 仅简单支持一个占位符的格式
		String strVal = value;//属性值
		StringBuffer buf = new StringBuffer(strVal);//缓冲字符串里
		int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);//这俩值是我们属性占位符的两边${    }
		int endIndex = strVal.indexOf(PLACEHOLDER_SUFFIX);//这两段代码就是去我们属性里找这俩值。如果没有就都返回-1

		if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) {
			//如果不是都等于-1，我们这里就是占位符的两个下标
			String propKey = strVal.substring(startIndex + 2, endIndex);//对属性占位符里面的值切割出来（brand）
			String propVal = properties.getProperty(propKey);//在我们的配置文件里，找到对应的值（brand的value）
			buf.replace(startIndex, endIndex + 1, propVal);//然后替换
		}
		return buf.toString();
	}

	public void setLocation(String location) {
		this.location = location;
	}

	private class PlaceholderResolvingStringValueResolver implements StringValueResolver {

		private final Properties properties;

		public PlaceholderResolvingStringValueResolver(Properties properties) {
			this.properties = properties;
		}

		public String resolveStringValue(String strVal) throws BeansException {
			return PropertyPlaceholderConfigurer.this.resolvePlaceholder(strVal, properties);
		}
	}
}
